// The NSpring Framework for .NET
// Copyright (c) 2003, Jeffrey Varszegi
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
// 
// - Redistributions of source code must retain the above copyright notice, this 
// list of conditions and the following disclaimer.
// 
// - Redistributions in binary form must reproduce the above copyright notice, this 
// list of conditions and the following disclaimer in the documentation and/or other 
// materials provided with the distribution.
// 
// - Neither the name of the NSpring project nor the names of its contributors may 
// be used to endorse or promote products derived from this software without 
// specific prior written permission from the copyright owner.
// 
// - No product derived from this software may be called "NSpring", nor may 
// "NSpring" appear in the name of such a product, without specific prior written 
// permission from the copyright owner.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
// OF THE POSSIBILITY OF SUCH DAMAGE. 

namespace NSpring.Logging {

using System;
using System.Collections;
using System.Text;

using NSpring.Logging.Common;
using NSpring.Logging.DataFormatters;

/// <summary>
/// <para>
/// Each DataFormatter object is able to format data objects (which occur as the Data 
/// property of Event objects) in a specific way.  Each EventFormatter has exactly 
/// one DataFormatter, and DataFormatters are not shared between EventFormatter instances.
/// </para>
/// <para>
/// Formatting of each primitive type (as well as the following structs: DateTime, Decimal, and
/// TimeSpan) can be controlled by setting format strings on various properties (for example, 
/// FloatFormat, IntegerFormat, DateTimeFormat, TrueValue, etc.).
/// </para>
/// <para>
/// In general, DataFormatter objects consider collections to be of two types: maps (such 
/// as dictionary objects) and lists (arrays and IEnumerable objects).  The output of each collection
/// may be presented in its natural order or in sorted order; this behavior is controlled by
/// use of the IsKeySortingEnabled and IsListSortingEnabled, and the one-shot setter
/// IsSortingEnabled (which sets both strategies).
/// </para>
/// <para>
/// TypeFormatter instances may be added to each DataFormatter by use of the AddTypeFormatter
/// method.  This allows for use of each data formatter with special hand-crafted formatting
/// routines for dealing with special data types.
/// </para>
/// <para>
/// All built-in subclasses of DataFormatter occur in the NSpring.Logging.DataFormatters 
/// namespace.
/// </para>
/// </summary>
public abstract class DataFormatter {
    protected readonly static Type STRING               = Type.GetType("System.String");
    protected readonly static Type BOOLEAN              = Type.GetType("System.Boolean");
    protected readonly static Type BYTE                 = Type.GetType("System.Byte");
    protected readonly static Type SBYTE                = Type.GetType("System.SByte");
    protected readonly static Type INT16                = Type.GetType("System.Int16");
    protected readonly static Type UINT16               = Type.GetType("System.UInt16");
    protected readonly static Type INT32                = Type.GetType("System.Int32");
    protected readonly static Type UINT32               = Type.GetType("System.UInt32");
    protected readonly static Type INT64                = Type.GetType("System.Int64");
    protected readonly static Type UINT64               = Type.GetType("System.UInt64");
    protected readonly static Type CHAR                 = Type.GetType("System.Char");
    protected readonly static Type DOUBLE               = Type.GetType("System.Double");
    protected readonly static Type SINGLE               = Type.GetType("System.Single");
    protected readonly static Type DECIMAL              = Type.GetType("System.Decimal");
    protected readonly static Type DATE_TIME            = Type.GetType("System.DateTime");
    protected readonly static Type TIMESPAN             = Type.GetType("System.TimeSpan");
    protected readonly static Type XML_DOCUMENT         = Type.GetType("System.Xml.XmlDocument");
    
    private const string DEFAULT_TRUE_VALUE             = "true";
    private const string DEFAULT_FALSE_VALUE            = "false";
    
    private int maximumDepth                            = 20;
    
    private Encoding encoding                           = Encoding.UTF8;
    
    private int typeFormatterCount                      = 0;
    private TypeFormatter[] typeFormatters              = new TypeFormatter[100];
    private Hashtable typeFormattersMap                 = new Hashtable(100);
    
    protected string trueValue                          = DEFAULT_TRUE_VALUE;
    protected string falseValue                         = DEFAULT_FALSE_VALUE;
    
    protected string dateTimeFormat                     = "";
    protected string decimalFormat                      = null;
    protected string doubleFormat                       = null;
    protected string floatFormat                        = null;
    protected string integerFormat                      = null;
    protected string longFormat                         = null;
    
    protected bool isKeySortingEnabled                  = false;
    protected bool isListSortingEnabled                 = false;
    
    protected object monitor                            = new object();

    protected DataFormatter() {}
    
    /// <summary>
    /// Gets/sets the maximum depth of complex data structures that will be formatted.
    /// The default is 10
    /// </summary>
    public int MaximumDepth {
        get {
            return maximumDepth;
        }
        set {
            maximumDepth = Math.Max(0, value);
        }
    }

    /// <summary>
    /// Gets and sets the value output for boolean 'true'
    /// </summary>
    public string TrueValue {
        get {
            return trueValue;
        }
        set {
            if (value == null) {
                trueValue = DEFAULT_TRUE_VALUE;
            }
            else {
                trueValue = value;
            }
        }
    }

    /// <summary>
    /// Gets and sets the value output for boolean 'false'
    /// </summary>
    public string FalseValue {
        get {
            return falseValue;
        }
        set {
            if (value == null) {
                falseValue = DEFAULT_FALSE_VALUE;
            }
            else {
                falseValue = value;
            }
        }
    }
    
    /// <summary>
    /// Gets and sets the format string used in formatting
    /// DateTime 
    /// values
    /// </summary>
    public string DateTimeFormat {
        get {
            return dateTimeFormat;
        }
        set {
            if (value != null) {
                DateTime.Now.ToString(value);
            }
            dateTimeFormat = value;
        }
    }
    
    /// <summary>
    /// Gets and sets the format string used in formatting
    /// decimal 
    /// values
    /// </summary>
    public string DecimalFormat {
        get {
            lock (monitor) {
                return decimalFormat;
            }
        }
        set {
            if (value != null) {
                (1000.0001m).ToString(value);
            }
            lock (monitor) {
                decimalFormat = value;
                if (decimalFormat != null) {
                    if (floatFormat == null) {
                        floatFormat = decimalFormat;
                    }
                    if (doubleFormat == null) {
                        doubleFormat = decimalFormat;
                    }
                }
            }
        }
    }
    
    /// <summary>
    /// Gets and sets the format string used in formatting
    /// double 
    /// values
    /// </summary>
    public string DoubleFormat {
        get {
            lock (monitor) {
                return doubleFormat;
            }
        }
        set {
            if (value != null) {
                Decimal.MaxValue.ToString(value);
                Decimal.MinValue.ToString(value);
            }
            lock (monitor) {
                doubleFormat = value;
                if (doubleFormat != null) {
                    if (floatFormat == null) {
                        floatFormat = doubleFormat;
                    }
                    if (decimalFormat == null) {
                        decimalFormat = doubleFormat;
                    }
                }
            }
        }
    }
    
    /// <summary>
    /// Gets and sets the format string used in formatting
    /// float 
    /// values
    /// </summary>
    public string FloatFormat {
        get {
            lock (monitor) {
                return floatFormat;
            }
        }
        set {
            if (value != null) {
                Single.MaxValue.ToString(value);
                Single.MinValue.ToString(value);
            }
            lock (monitor) {
                floatFormat = value;
                if (floatFormat != null) {
                    if (doubleFormat == null) {
                        doubleFormat = floatFormat;
                    }
                    if (decimalFormat == null) {
                        decimalFormat = floatFormat;
                    }
                }
            }
        }
    }
    
    /// <summary>
    /// Gets and sets the format string used in formatting
    /// int and uint 
    /// values
    /// </summary>
    public string IntegerFormat {
        get {
            lock (monitor) {
                return integerFormat;
            }
        }
        set {
            if (value != null) {
                Int32.MaxValue.ToString(value);
                Int32.MinValue.ToString(value);
                UInt32.MaxValue.ToString(value);
                UInt32.MinValue.ToString(value);
            }
            lock (monitor) {
                integerFormat = value;
                if ((integerFormat != null) && (longFormat == null)) {
                    longFormat = integerFormat;
                }
            }
        }
    }

    /// <summary>
    /// Gets and sets the format string used in formatting
    /// long and ulong 
    /// values
    /// </summary>
    public string LongFormat {
        get {
            lock (monitor) {
                return longFormat;
            }
        }
        set {
            if (value != null) {
                Int64.MaxValue.ToString(value);
                Int64.MinValue.ToString(value);
                UInt64.MaxValue.ToString(value);
                UInt64.MinValue.ToString(value);
            }
            lock (monitor) {
                longFormat = value;
                if ((longFormat != null) && (integerFormat == null)) {
                    integerFormat = longFormat;
                }
            }
        }
    }

    /// <summary>
    /// Adds the specified object-type formatter to this instance
    /// </summary>
    /// <param name="typeFormatter">The object formatter to add</param>
    public void AddTypeFormatter(TypeFormatter typeFormatter) {
        if (typeFormatter == null) {
            throw new ArgumentNullException();
        }
        Type dataType = typeFormatter.SupportedType;
        if (dataType == null) {
            throw new ArgumentException("No supported data type");
        }
        lock (monitor) {
            if (typeFormattersMap.ContainsKey(dataType)) {
                throw new InvalidOperationException("Type " + dataType.ToString() + " already has a supporting TypeFormatter");
            }
            typeFormatter.DataFormatter = this;
            typeFormattersMap.Add(dataType, typeFormatter);
            if (typeFormatterCount == typeFormatters.Length) {
                TypeFormatter[] newTypeFormatters = new TypeFormatter[typeFormatterCount * 2];
                Array.Copy(typeFormatters, 0, newTypeFormatters, 0, typeFormatterCount);
                typeFormatters = newTypeFormatters;
            }
            typeFormatters[typeFormatterCount++] = typeFormatter;
        }
    }
    
    /// <summary>
    /// Gets an array containing all TypeFormatter objects set on this instance
    /// </summary>
    public TypeFormatter[] TypeFormatters {
        get {
            lock (monitor) {
                TypeFormatter[] returnValue = new TypeFormatter[typeFormatterCount];
                Array.Copy(typeFormatters, 0, returnValue, 0, typeFormatterCount);
                return returnValue;
            }
        }
    }
    
    /// <summary>
    /// Returns the TypeFormatter associated with the given type
    /// </summary>
    /// <param name="dataType">The data type for which to find the TypeFormatter set on this instance</param>
    /// <returns>The TypeFormatter mapped to the data type, or null if no mapping exists</returns>
    public TypeFormatter GetTypeFormatter(Type dataType) {
        if (typeFormatterCount <= 100) {
            for(int x = 0; x < typeFormatterCount; x++) {
                if (typeFormatters[x].SupportedType == dataType) {
                    return typeFormatters[x];
                }
            }
            return null;
        }
        else {
            lock (monitor) {
                return (TypeFormatter)typeFormattersMap[dataType];
            }
        }
    }
    
    /// <summary>
    /// Gets and sets the text encoding used to format data
    /// </summary>
    public Encoding Encoding {
        get {
            return encoding;
        }
        set {
            if (value == null) {
                throw new ArgumentNullException();
            }
            lock (monitor) {
                if (UpdateEncoding(value)) {
                    encoding = value;
                }
                else {
                    throw new ArgumentException("Invalid encoding");
                }
            }
        }
    }

    protected virtual bool UpdateEncoding(Encoding _newEncoding) {
        return true;
    }

    /// <summary>
    /// Gets and sets whether the contents of dictionaries, hashtables, and other mapping collections
    /// are output in key-sorted order.  The default is false
    /// </summary>
    public bool IsKeySortingEnabled {
        get {
            return isKeySortingEnabled;
        }
        set {
            isKeySortingEnabled = value;
        }
    }
    
    
    /// <summary>
    /// Gets and sets whether the contents of arrays and list collections
    /// are output in sorted order.  The default is false
    /// </summary>
    public bool IsListSortingEnabled {
        get {
            return isListSortingEnabled;
        }
        set {
            isListSortingEnabled = value;
        }
    }
    
    /// <summary>
    /// A convenience property that allows the setting of both IsKeySortingEnabled
    /// and IsListSortingEnabled with a single statement
    /// </summary>
    public bool IsSortingEnabled {
        set {
            isKeySortingEnabled = value;
            isListSortingEnabled = value;
        }
    }
    
    /// <summary>
    /// Sorts an array, returning the elements as a new array.  In-place sorting is not
    /// performed because an event may be formatted by several threads at once
    /// </summary>
    /// <param name="array">The array to sort</param>
    /// <returns>A new array containing the sorted elements</returns>
    protected static object[] Sort(Array array) {
        object[] sortedArray = new object[array.Length];
        array.CopyTo(sortedArray, 0);
        try { Array.Sort(sortedArray); } catch {}
        return sortedArray;
    }
    
    /// <summary>
    /// Sorts a list or other "enumerable" collection
    /// </summary>
    /// <param name="enumerable">The collection to sort</param>
    /// <returns>An array containing the sorted elements</returns>
    protected static object[] Sort(IEnumerable enumerable) {
        ArrayList arrayList = new ArrayList(50);
        IEnumerator enumerator = enumerable.GetEnumerator();
        while (enumerator.MoveNext()) {
            arrayList.Add(enumerator.Current);
        }
        object[] sortedElements = new object[arrayList.Count];
        arrayList.CopyTo(sortedElements);
        try { Array.Sort(sortedElements); } catch {}
        return sortedElements;
    }
    
    /// <summary>
    /// Gets the elements of a list or other "enumerable" collection
    /// </summary>
    /// <param name="enumerable">The collection</param>
    /// <returns>An array containing the elements</returns>
    protected static object[] GetElements(IEnumerable enumerable) {
        ArrayList arrayList = new ArrayList(50);
        IEnumerator enumerator = enumerable.GetEnumerator();
        while (enumerator.MoveNext()) {
            arrayList.Add(enumerator.Current);
        }
        object[] elements = new object[arrayList.Count];
        arrayList.CopyTo(elements);
        return elements;
    }
    
    /// <summary>
    /// Sorts the keys of a dictionary, hashtable or other mapping collection
    /// </summary>
    /// <param name="dictionary">The dictionary for which to sort the keys</param>
    /// <returns>An array containing the sorted keys</returns>
    protected object[] SortKeys(IDictionary dictionary) {
        ArrayList arrayList = new ArrayList(dictionary.Count);
        IEnumerator enumerator = dictionary.Keys.GetEnumerator();
        while (enumerator.MoveNext()) {
            arrayList.Add(enumerator.Current);
        }
        object[] sortedElements = new object[arrayList.Count];
        arrayList.CopyTo(sortedElements);
        try { Array.Sort(sortedElements); } catch {}
        return sortedElements;
    }
    
    /// <summary>
    /// Gets the keys of a dictionary, hashtable or other mapping collection
    /// </summary>
    /// <param name="dictionary">The dictionary for which to get the keys</param>
    /// <returns>An array containing the keys</returns>
    protected object[] GetKeys(IDictionary dictionary) {
        ArrayList arrayList = new ArrayList(dictionary.Count);
        IEnumerator enumerator = dictionary.Keys.GetEnumerator();
        while (enumerator.MoveNext()) {
            arrayList.Add(enumerator.Current);
        }
        object[] elements = new object[arrayList.Count];
        arrayList.CopyTo(elements);
        return elements;
    }
    
    private static string timeZone                          = Constants.ISO_8601_TIME_ZONE;
    private static string daylightSavingsTimeZone           = Constants.ISO_8601_DAYLIGHT_SAVINGS_TIME_ZONE;
    protected static string GetLocalTimeZone(int month, int day) {
        switch (month) {
            case 1       :
            case 2       :
            case 3       : {
                return daylightSavingsTimeZone;
            }
            case 4       : {
                if (day > 2) {
                    return timeZone;
                }
                else {
                    return daylightSavingsTimeZone;
                }
            }
            case 5       :
            case 6       :
            case 7       :
            case 8       :
            case 9       : {
                return timeZone;
            }
            case 10      : {
                if (day < 30) {
                    return timeZone;
                }
                else {
                    return daylightSavingsTimeZone;
                }
            }
            case 11      :
            case 12      : {
                return daylightSavingsTimeZone;
            }
            default      : {
                return timeZone;
            }
        }
    }
    
    /// <summary>
    /// Formats the specified data object in an implementation-specific way
    /// </summary>
    /// <param name="o">The data object to format</param>
    /// <returns>A string representation of the data object</returns>
    public abstract string Format(object o);

    /// <summary>
    /// Formats the specified data object in an implementation-specific way
    /// </summary>
    /// <param name="o">The data object to format</param>
    /// <param name="sb">The string buffer to which to append the formatted data</param>
    public abstract void Format(object o, StringBuilder sb);
    
    /// <summary>
    /// Performs a deep copy; used to ensure that no DataFormatter is used
    /// by more than a single EventFormatter
    /// </summary>
    /// <returns>A deep copy of this instance</returns>
    public abstract DataFormatter Copy();
    
}

}
